﻿using System.Collections.Generic;
using System.Diagnostics;
using Flatland.GeneticProgramming.CoreTypes;
using Flatland.GeneticProgramming.SyntaxTree;
using Flatland.Utilities;

namespace Flatland.GeneticProgramming.Crossovers {
    class OnePointCrossover : ICrossOver{

        public OnePointCrossover() { }

        public void Cross(List<Report> reports, double crossOverRate, int maxTreeDepth)
        {
            Debug.Assert(reports != null, "Argument was null", "reports in Crossover was null");
            Debug.Assert(0.0d <= crossOverRate, "probability was less than zero");
            Debug.Assert(1 < reports.Count, "Illegal reports for cross over", "At leat 2 reports are required to perform cross over");       

            int count = reports.Count % 2 == 0 ? reports.Count : reports.Count - 1;
            for (int i = 0; i < count; i += 2)
            {
                Report a = reports[Utils.Random.Next(reports.Count)];
                Report b = reports[Utils.Random.Next(reports.Count)];
                if (a == b) b = reports.Find(r => r != a);
                if ((double)Utils.Random.Next(101) / 100 <= crossOverRate)
                    OnePointCrossOver(a.GenoType.Tree, b.GenoType.Tree, maxTreeDepth);
            }
        }


        /// <summary>
        /// Create a new Tree with one point cross over.
        /// Crossover may be unsuccesful, maybe due to no <i>common region</i> or exessive intents to find such, in which case Tree parent 1 will be returned.
        /// The algorithm:
        /// Node s = null
        /// while child c1 in t1 and s is null
        /// pick p as c1.randomNode
        /// for each child c2 in t2
        /// find substitute s by recursive search c2.randomNode equal p
        /// if s is null recursive search c2
        /// 
        /// </summary>
        /// <param name="t1">Tree parent 1</param>
        /// <param name="t2">Tree parent 1</param>
        public void OnePointCrossOver(Tree t1, Tree t2, int maxTreeDepth) {
            if(t1.GetType() == typeof(Leaf) || (t2.GetType() == typeof(Leaf)))
                return;

            Tree substitute = null;
            Tree prospect = null;

            //For each sbubTree in the first Tree, we get a random Node in t1 at look for a substitude in t2.
            //If we havent found a substitue after trying one time for each child of t1 we simply give up
            for (int i = 0; i < ((Node)t1).Children.Count; i++) {
                if (substitute != null) break;
                prospect = ((Node)t1).Children[i].GetRandomNode();
                for (int j = 0; j < ((Node)t2).Children.Count; j++){
                    substitute = _OnePointCrossOver(prospect, ((Node)t2).Children[j].GetRandomNode(), maxTreeDepth) ??
                                 _OnePointCrossOver(prospect, ((Node)t2).Children[j], maxTreeDepth);
                    if (substitute != null)
                        break;
                }
            }
        }

        /// <summary>
        /// Returns suitable Tree or null
        /// </summary>
        /// <param name="prospect">Prospect Tree</param>
        /// <param name="searchTree">Tree we are searching</param>
        /// <returns></returns>
        private Tree _OnePointCrossOver(Tree prospect, Tree searchTree, int maxTreeDepth) {
            Debug.Assert(prospect != null, "prospect was null in _OnePointCrossOver");
            Debug.Assert(searchTree != null, "searchTree was null in _OnePointCrossOver");

            //If equal and bloat control, the prospect will replace the search tree in its parent.
            //SearchTree is returned for replacing prospect in its parent
            if (prospect.Equals(searchTree) && Crossover.BloatControl(prospect, searchTree, maxTreeDepth)) {
                Node temp = (Node)prospect.Parent;
                ((Node) searchTree.Parent).Replace(searchTree, prospect);
                temp.Replace(prospect, searchTree);
                return searchTree;
            }

            //Last node or leaf could not crossover.
            //If we are still at a Node in the searched Tree we go through the children
            if (searchTree.GetType() == typeof(Node)) {
                foreach (Tree child in ((Node)searchTree).Children) {
                    Tree substitute = _OnePointCrossOver(prospect, child, maxTreeDepth);
                    if (substitute != null)
                        return substitute;
                }
            }
            return null; //No suitable subTree
        }
    }
}
